/**
 * @file WebGL 相关工具方法
 */
export type Shader = WebGLShader | null
export type Buffer = WebGLBuffer | null
export type Texture = WebGLTexture | null
export type Program = WebGLProgram | null
export type ShaderProgram = WebGLProgram | null
export type UniformLocation = WebGLUniformLocation | null
/**
 * 创建指定类型的着色器，上传 source 源码并编译
 * loadShader 函数将 WebGL 上下文，着色器类型和源码作为参数输入，然后按如下步骤创建和编译着色器：

调用gl.createShader().创建一个新的着色器。

调用gl.shaderSource().将源代码发送到着色器。

一旦着色器获取到源代码，就使用gl.compileShader().进行编译。

为了检查是否成功编译了着色器，将检查着色器参数 gl.COMPILE_STATUS 状态。通过调用gl.getShaderParameter()获得它的值，并指定着色器和我们想要检查的参数的名字（gl.COMPILE_STATUS）。如果返回错误，则着色器无法编译，因此通过gl.getShaderInfoLog()从编译器中获取日志信息并 alert，然后删除着色器返回 null，表明加载着色器失败。

如果着色器被加载并成功编译，则返回编译的着色器。
 * @param { WebGLRenderingContext } gl
 * @param { number } type
 * @param { String } source
 * @returns { WebGLShader | null }
 */
export const loadShader = (
  gl: WebGLRenderingContext,
  type: number,
  source: string): Shader => {
  try {
    const shader = gl.createShader(type)
    if (!shader) {
      console.error('An error occurred creating the shader.')
      return null
    }
    // Send the source to the shader object
    gl.shaderSource(shader, source)
    // Compile the shader program
    gl.compileShader(shader)
    // See if it compiled successfully
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      console.error(
        "An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader),
      )
      gl.deleteShader(shader)
      return null
    }

    return shader
  } catch (e) {
    console.error(e)
    return null
  }
}

/**
 * 初始化着色器程序，让 WebGL 知道如何绘制我们的数据
 * @param { WebGLRenderingContext } gl
 * @param { String } vsSource
 * @param { String } fsSource
 */
export const initShaderProgram = (
  gl: WebGLRenderingContext,
  vsSource: String,
  fsSource: String): ShaderProgram => {
  try {
    const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource as string)
    const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource as string)
    if (!vertexShader || !fragmentShader) {
      return null
    }
    // 创建着色器程序
    const shaderProgram = gl.createProgram()
    if (!shaderProgram) {
      console.error('An error occurred creating the shader program.')
      return null
    }
    // Attach a vertex shader
    gl.attachShader(shaderProgram, vertexShader)
    gl.attachShader(shaderProgram, fragmentShader)
    // Link the program
    gl.linkProgram(shaderProgram)
    // Create a program object
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      console.error(
        "Unable to initialize the shader program: " +
        gl.getProgramInfoLog(shaderProgram),
      )
      return null
    }
    return shaderProgram
  } catch (e) {
    console.error(e)
    return null
  }
}
/**
 * 给顶点着色
在 WebGL 中，物体是由一系列顶点组成的，每一个顶点都有位置和颜色信息。在默认情况下，所有像素的颜色（以及它所有的属性，包括位置）都由线性插值计算得来，自动形成平滑的渐变。我们以前的顶点着色器没有给顶点添加任何特定的颜色——在顶点着色器与片段着色器之间给每个像素着白色，于是整个正方形被渲染成纯白。

现在我们假设正方形的每个顶点使用不同的颜色：红，黄，绿，白，以此渲染一个渐变的色彩。第一步，要给这些顶点建立相应的颜色。首先我们要创建一个顶点颜色数组，然后将它们存在 WebGL 的缓冲区中。为实现这一功能，我们在 initBuffers() 函数中加入如下代码：

这段代码首先建立了一个 JavaScript 的数组，此数组中包含四组四值向量，每一组向量代表一个顶点的颜色。然后，创建一个 WebGL 缓冲区用来存储这些颜色——将数组中的值转换成 WebGL 所规定的浮点型后，存储在该缓冲区中。
 * @param { WebGLRenderingContext } gl
 * @returns { WebGLBuffer | null }
 */
export const initColorBuffer = (gl: WebGLRenderingContext): Buffer => {
  const faceColors = [
    [1.0, 1.0, 1.0, 1.0], // Front face: white
    [1.0, 0.0, 0.0, 1.0], // Back face: red
    [0.0, 1.0, 0.0, 1.0], // Top face: green
    [0.0, 0.0, 1.0, 1.0], // Bottom face: blue
    [1.0, 1.0, 0.0, 1.0], // Right face: yellow
    [1.0, 0.0, 1.0, 1.0], // Left face: purple
  ];
  let colors: number[] = []
  // Convert the array of colors into a table for all the vertices.
  for (let j = 0; j < faceColors.length; ++j) {
    const c = faceColors[j];
    // Repeat each color four times for the four vertices of the face
    colors = colors.concat(c, c, c, c);
  }
  const colorBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW)
  return colorBuffer
}
/**
 * 初始化一个缓冲区对象
 * @param { WebGLRenderingContext } gl
 * @param { Float32Array } data
 * @returns { WebGLBuffer | null }
 */
export const initBuffers = (gl: WebGLRenderingContext): Buffer => {
  try {
    const positionBuffer = initPositionBuffer(gl)
    const colorBuffer = initColorBuffer(gl)
    const indexBuffer = initIndexBuffer(gl)
    return {
      position: positionBuffer,
      color: colorBuffer,
      indices: indexBuffer,
    }
  } catch (e) {
    console.error(e)
    return null
  }
}

/**
 * 创建元素（三角形）数组了
 * @param { WebGLRenderingContext } gl
 * @returns { WebGLBuffer | null }
 */
export const initIndexBuffer = (
  gl: WebGLRenderingContext
): Buffer => {
  const indexBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
  // This array defines each face as two triangles, using the
  // indices into the vertex array to specify each triangle's
  // position.
  // indices 数组声明每一个面都使用两个三角形来渲染。通过立方体顶点数组的索引指定每个三角形的顶点。那么这个立方体就是由 12 个三角形组成的了。
  // 每个三角形的顶点索引顺序是逆时针的，这样可以保证三角形的正面是向外的。
  const indices = [
    0, 1, 2, 0, 2, 3, // front
    4, 5, 6, 4, 6, 7, // back
    8, 9, 10, 8, 10, 11, // top
    12, 13, 14, 12, 14, 15, // bottom
    16, 17, 18, 16, 18, 19, // right
    20, 21, 22, 20, 22, 23, // left
  ]
  // Now send the element array to GL
  gl.bufferData(
    gl.ELEMENT_ARRAY_BUFFER,
    new Uint16Array(indices),
    gl.STATIC_DRAW,
  )
  return indexBuffer
}
/**
 * 初始化一个位置缓冲区对象
 * 这段代码简单给出了绘画场景的本质。首先，它调用 gl 的成员函数 createBuffer() 得到了缓冲对象并存储在顶点缓冲器。然后调用 bindBuffer() 函数绑定上下文。
 * 当上一步完成，我们创建一个 Javascript 数组去记录每一个正方体的每一个顶点。然后将其转化为 WebGL 浮点型类型的数组，并将其传到 gl 对象的 bufferData() 方法来建立对象的顶点。
 * @param { WebGLRenderingContext } gl
 * @returns { WebGLBuffer | null }
 */
export const initPositionBuffer = (gl: WebGLRenderingContext): Buffer => {
  try {
    // Create a buffer for the square's positions.
    const positionBuffer = gl.createBuffer()
    // Select the positionBuffer as the one to apply buffer
    // operations to from here out.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
    // Now create an array of positions for the square.
    // 逆时针坐标点(1.0, 1.0) (-1.0, 1.0) (1.0, -1.0) (-1.0, -1.0)
    const positions = [
      // Front face
      -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,

      // Back face
      -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,

      // Top face
      -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,

      // Bottom face
      -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,

      // Right face
      1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0,

      // Left face
      -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,
    ]
    // Now pass the list of positions into WebGL to build the
    // shape. We do this by creating a Float32Array from the
    // JavaScript array, then use it to fill the current buffer.
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)

    return positionBuffer
  } catch (e) {
    console.error(e)
    return null
  }
}

// 告诉 WebGL 如何从缓冲区中提取位置数据
export const setPositionAttribute = (gl: WebGLRenderingContext, buffers: any, programInfo: any) => {
  const numComponents = 3; // pull out 2 values per iteration
  const type = gl.FLOAT; // the data in the buffer is 32bit floats
  const normalize = false; // don't normalize
  const stride = 0; // how many bytes to get from one set of values to the next
  // 0 = use type and numComponents above
  const offset = 0; // how many bytes inside the buffer to start from
  gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
  gl.vertexAttribPointer(
    programInfo.attribLocations.vertexPosition,
    numComponents,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
}
// Tell WebGL how to pull out the colors from the color buffer
// into the vertexColor attribute.
export const setColorAttribute = (gl: WebGLRenderingContext, buffers: any, programInfo: any) => {
  const numComponents = 4;
  const type = gl.FLOAT;
  const normalize = false;
  const stride = 0;
  const offset = 0;
  gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
  gl.vertexAttribPointer(
    programInfo.attribLocations.vertexColor,
    numComponents,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
}
